/*
 * Copyright 2012 Alan Burlison, alan@bleaklow.com.  All rights reserved.
 * Use is subject to license terms.  See LICENSE.txt for details.
 */

/*
 * LIS302DL accelerometer.
 */

#include <stdlib.h>
#include <avr/interrupt.h>
#include "LIS302DL.h"

// Duemilanove.
#if defined(__AVR_ATmega328P__)

#define INT_PORT        PORTD
#define INT_DDR         DDRD
#define INT_PIN         PIND
#define INT0_BIT        PORTD2
#define INT1_BIT        PORTD3

// Mega.
#elif defined(__AVR_ATmega1280__)

#define INT_PORT        PORTD
#define INT_DDR         DDRD
#define INT_PIN         PIND
#define INT0_BIT        PORTD0
#define INT1_BIT        PORTD1

// Otherwise, unknown.
#else
#error "Unknown MCU type"
#endif

// Enable SPI, SPI master, SPI mode 3, CLK/4.
#define SPCR_VAL BIT(SPE) | BIT(MSTR) | SPI_MODE_3
#define SPSR_VAL 0x00

// Registers and register bits.
#define WHO_AM_I         0x0F
#define WHO_AM_I_VALUE   0x3B
#define CTRL_REG1        0x20
#define XEN              0
#define YEN              1
#define ZEN              2
#define STM              3
#define STP              4
#define FS               5
#define PD               6
#define DR               7
#define CTRL_REG2        0x21
#define HP_COEFF1        0
#define HP_COEFF2        1
#define HP_FF_WU1        2
#define HP_FF_WU2        3
#define FDS              4
#define BOOT             6
#define SIM              7
#define CTRL_REG3        0x22
#define I1CFG0           0
#define I1CFG1           1
#define I1CFG2           2
#define I2CFG0           3
#define I2CFG1           4
#define I2CFG2           5
#define PP_OD            6
#define IHL              7
#define HP_FILTER_RESET  0x23
#define ACCEL_STATUS     0x27
#define XDA              0
#define YDA              1
#define ZDA              2
#define ZYXDA            3
#define XOR              4
#define YOR              5
#define ZOR              6
#define ZYXOR            7
#define OUT_X            0x29
#define OUT_Y            0x2B
#define OUT_Z            0x2D
#define FF_WU_CFG_1      0x30
#define XLIE             0
#define XHIE             1
#define YLIE             2
#define YHIE             3
#define ZLIE             4
#define ZHIE             5
#define LIR              6
#define AOI              7
#define FF_WU_SRC_1      0x31
#define XLO              0
#define XHI              1
#define YLO              2
#define YHI              3
#define ZLO              4
#define ZHI              5
#define IA               6
#define FF_WU_THS_1      0x32
#define DCRM             7
#define FF_WU_DURATION_1 0x33
#define FF_WU_CFG_2      0x34
#define FF_WU_SRC_2      0x35
#define FF_WU_THS_2      0x36
#define FF_WU_DURATION_2 0x37
#define CLICK_CFG        0x38
#define SINGLE_X         0
#define DOUBLE_X         1
#define SINGLE_Y         2
#define DOUBLE_Y         3
#define SINGLE_Z         4
#define DOUBLE_Z         5
#define LIR              6
#define CLICK_SRC        0x39
#define CLICK_THSY_X     0x3B
#define CLICK_THSZ       0x3C
#define CLICK_TIME_LIMIT 0x3D
#define CLICK_LATENCY    0x3E
#define CLICK_WINDOW     0x3F

// Commands.
#define READ            0b10000000
#define WRITE           0b00000000
#define INC_ADDR        0b01000000
#define ADDR_MASK       0b00111111

static volatile uint8_t dataReady;

ISR(INT0_vect)
{
    dataReady = 1;
}

LIS302DL::LIS302DL(const pin_t *const _csn)
: SPIMaster(SPCR_VAL, SPSR_VAL, _csn),
  Task()
{
}

void LIS302DL::initPins(bool shared) {
    SPIInitPins(shared);
    EICRA = 0x00;
    EIMSK = 0x00;
    SET_BIT_LO(INT_DDR, INT0_BIT);                  // Input.
    SET_BIT_LO(INT_PORT, INT0_BIT);                 // No pullup.
    SET_BIT_HI(EIFR, INTF0);                        // Clear any pending.
    SET_MASK_HI(EICRA, BIT(ISC00) | BIT(ISC01));    // Rising edge.
    SET_BIT_HI(EIMSK, INT0);                        // Enable.
}

void LIS302DL::setMode(LIS302DL::Mode mode) {
    if (mode == Disabled) {
        writeRegister(CTRL_REG1, 0x00);
    } else {
        writeRegister(CTRL_REG1, BIT(PD) | BIT(XEN) | BIT(YEN) | BIT(ZEN));
        if (mode == Absolute) {
            writeRegister(CTRL_REG2, 0x00);

        } else {
            writeRegister(CTRL_REG2,
              BIT(FDS) | BIT(HP_FF_WU2) | BIT(HP_FF_WU1));
        }
        writeRegister(CTRL_REG2, 0x00);
        writeRegister(CTRL_REG3, BIT(I1CFG2));
    }
}

uint8_t LIS302DL::readRegister(uint8_t reg) {
    SPISelect();
    SPIWriteRead(READ | (reg & ADDR_MASK));
    uint8_t val = SPIWriteRead(0x00);
    SPIDeselect();
    return val;
}

void LIS302DL::writeRegister(uint8_t reg, uint8_t val) {
    SPISelect();
    SPIWriteRead(WRITE | (reg & ADDR_MASK));
    SPIWriteRead(val);
    SPIDeselect();
}

void LIS302DL::waitForData(void)
{
    while (! (readRegister(ACCEL_STATUS) & ZYXDA)) {
        // Spin wait.
    }
}

void LIS302DL::readData(LIS302DL::XYZ *xyz) {
    SPISelect();
    SPIWriteRead(READ | INC_ADDR | OUT_X);
    xyz->x = SPIWriteRead(0x00);
    SPIWriteRead(0x00);
    xyz->y = SPIWriteRead(0x00);
    SPIWriteRead(0x00);
    xyz->z = SPIWriteRead(0x00);
    SPIDeselect();
    dataReady = 0;
}

bool LIS302DL::canRun(uint32_t now) {
    uint8_t dr;
    uint8_t sreg = SREG;
    cli();
    dr = dataReady;
    SREG = sreg;
    return dr;
}